package Socket;

import org.omg.IOP.IOR;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;

/**
 * 聊天室服务端
 */
public class Server {
     /*
        运行在服务端的ServerSocket主要有两个作用
        1：向系统申请服务端口，客户端的Socket就是通过这个端口与服务器建立连接的
        2：监听服务端口，一旦一个客户端通过端口建立连接会自动创建一个Socket， 服务端1
        就可以通过这个Socket与客户端交互了

        如果我们把Socket比喻为电话，那么ServerSocket相当于是某个客服中心总机
     */

    private ServerSocket serverSocket;
    /*
        内部类客户端访问外部类的成员，因此在Server类上定义一个数组allOut可以被所有
        内部类ClientHandler实例访问，从而将这些ClientHandler实例之间相互访问的数据
        存放在这个数组中达到共享数据的目的
     */
    //private PrintWriter[] allOut = {};
    //private Collection<PrintWriter> allOut=new ArrayList<>();
    /*
    创建一个线程安全的集合
    线程安全的集合中，add，remove等方法上都直接使用了synchronized修饰，多个线程是不能
        同时调用一个集合的这些方法的，保证同步执行。
     */
    private List<PrintWriter> allOut = Collections.synchronizedList(new ArrayList<>());

    public Server() {
        try {
            System.out.println("正在启动服务端......");
            /*
              实例化是要指定服务端口，如果该端口被当前系统其他应用程序占用时，会
              抛出异常：java.net.BindException:address already in use
             */
            serverSocket = new ServerSocket(8081);
            System.out.println("服务端启动完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            /*
               ServerSocket提供的方法：
               Socket accept（）
               该方法是一个阻塞方法，调用后程序进入阻塞状态（”卡住“），直到一个客户端
               实例化Socket与当前客户端建立连接，此时accept方法会立即返回一个Socket
               实例，服务端通过它就可以与客户端交互了。


               可以理解为这个动作相当于是总机的”接电话“操作。
             */
            while (true) {
                System.out.println("等待客户端连接.....");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
//             启动一个线程来负责与该客户端交互
                //1 创建线程任务
                ClientHandler handler = new ClientHandler(socket);
                //创建一个线程并执行该任务
                Thread t = new Thread(handler);
                // 3启动该线程
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();

    }

    /**
     * 该线程任务用于指定客户端进行交互。
     * 每个链接服务端的客户端都是通过该线程进行交互的。即：一个客户端靠一个该线程进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;

        public ClientHandler(Socket socket) {
            this.socket = socket;
            // 通过socket获取远端计算机地址信息（对于服务端而言，远端就是客户端）
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;//放在这里为了能在finally中看到并处理
            try {
/*
             Socket提供的方法：
             InputStream  getInputStream()
             通过该方法获取的字节输入读取的是远端计算机发送过来的数据。

             这里相当于是读取当前服务端中这个Socket对应的远端（客服端）那边Socket获取
             的输出流写出的字节数据。
 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                pw = new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(),
                                        "UTF-8"
                                )
                        ), true
                );
                //     将该输出流存入共享数组allOut中，便于其他ClientHandler将消息发送给当前客户端
                //             synchronized (Server.this) {
                //         1对allOut数组扩容
                //    Collection allOut =new ArrayList();
                //           allOut.add(pw);
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
//                    //          2：将输出流存入数组的最后一个位置
//                    allOut[allOut.length - 1] = pw;
                //            }
                allOut.add(pw);
                System.out.println(host + "上线了，当前在线的人数：" + allOut.size());
                sendMessage(host + "上线了，当前在线的人数：" + allOut.size());//广播上线通知

                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(host + "说了：" + line);
                    //将消息回复给所有客户端
                    sendMessage(host + "说了：" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开连接后的操作

                //将当前客户端的输出流从allOut数组中删除
//                 synchronized (Server.this) {
//                    for (int i = 0; i < allOut.size(); i++) {
//                        if (allOut[i] == pw) {
//                            allOut[i] = allOut[allOut.size() - 1];
//                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                            break;
//                            }
//                        }
//                    }
                allOut.remove(pw);

                sendMessage(host + "下线了，当前在线人数：" + allOut.size());
                try {
                    //关闭socket释放资源
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }//  run()方法结束

        /**
         * @param message
         */
        private void sendMessage(String message) {
//            synchronized (allOut) {
//                for (PrintWriter pw : allOut) {
//                    pw.println(message);
//                }
//            }
            allOut.forEach(pw->pw.println(message));
        }
    }
}